/**
 * Neighbor.java
 *
 * @author Hagen Boehm
 */


package SSF.OS.OSPFv2;


import java.util.*;
import SSF.OS.Timer;
import SSF.OS.IpHeader;
import SSF.OS.ProtocolException;
import SSF.OS.ProtocolMessage;
import SSF.Net.Util.IP_s;

// ========================= class SSF.OS.OSPFv2.Neighbor ============================== //
/**
 * The class Neighbor represents a conversation between its interface owner and the
 * neighboring interface. All Information to build and maintain this conversation is 
 * represented in it. It is identified either by the neighboring router's OSPF Router
 * ID or by its Neighbor ip address.
 */
public class Neighbor {

  // ................. constants ...................... //
  
  /** Indicates that the current state of neighbor conversation is DOWN */
  public static final int DOWN     = 1;

  /** Indicates that the current state of neighbor conversation is ATTEMPT */
  public static final int ATTEMPT  = 2;

  /** Indicates that the current state of neighbor conversation is INIT */
  public static final int INIT     = 3;

  /** Indicates that the current state of neighbor conversation is 2-WAY */
  public static final int TWOWAY   = 4;

  /** Indicates that the current state of neighbor conversation is EXSTART */
  public static final int EXSTART  = 5;

  /** Indicates that the current state of neighbor conversation is EXCHANGE */
  public static final int EXCHANGE = 6;

  /** Indicates that the current state of neighbor conversation is LOADING */
  public static final int LOADING  = 7;

  /** Indicates that the current state of neighbor conversation is FULL */
  public static final int FULL     = 8;
 
  // .................. member data ................... //

  /** A link to the OSPF ProtocolSession this Neighbor belongs to. */
  OSPF myOSPF;

  /** A link to the OSPF-Interface this Neighbor belongs to. */
  Interface myInterface;

  /** A link to the OSPF-Area this Neighbor Data Structure belongs to. */
  AreaData myArea;
  
  /** The functional level of the neighbor conversation */
  public int state;

  /** To know who is master in the master/slave relationship during Database Exchage.
   *  false means neighbor is slave, true means neighbor is master.
   */
  public boolean isMaster;

  /** The DD Sequence Number of the DD packet that is currently being sent to the neighbor */
  public int DDSeqNum;
  
  /** Another data structure for keeping information of the last received DD packet. */
  public LastRecDDPacket lastDDPacket;

  /** The Router priority of the neighboring router. */
  public int neighborPrio;

  /** The router ID of the Neighbor Router. */
  public int neighborID;

  /** The NHI address prefix of the neighboring router. */
  public String neighborNHI;

  /** The IP address of the neighboring router's interface to the attached network. */
  public int neighborIPAddr;

  /** The optional OSPF capabilities supported by the neighbor. */
  public boolean[] options = new boolean[8];

  /** The neighbor's IDEA of the designated router. */
  public int neighborDR;

  /** The neighbor's IDEA of the backup designated router. */
  public int neighborBDR;

  /** The list of LSAs that have been flooded but not yet acknowledged on this adjacency.
   *  It is implemented as HashMap because it should be easy to replace old instances 
   *  of LSAs by new ones.
   */ 
  public HashMap lsRetransList = new HashMap();

  /** The complete list of LSAs that make up the area link-state database, at the moment the
   *  neighbor goes into Database Exchange state (EXCHANGE). It is implemented as Vector
   *  because all LSAs stored in it are unique at every time.
   */
  public Vector dbSummList = new Vector();

  /** The list of LSAs that need to be received from this neighbor in order to synchronize
   *  the two neighbor's link-state database. */
  public Hashtable lsReqList = new Hashtable();

  /** The list of LSAs that must be flooded out to this neighbor when the pacingTimer fires. 
   *  When this happened they are transferred to the lsRetransList.
   */
  public HashMap pacingFloodList = new HashMap();

  /** A Database Description Packet. Used, to exchange LS Database informations with the 
   *  Neighbor when both became adjacent. It saves the last sent DD Packet.
   */
  public DD_Packet dataDescContent;

  /** A Link State Request Packet. Used to request more recent LSAs from the Neighbor when
   *  state is EXCHANGE, LOADING or FULL. It saves the last sent LS Request Packet.
   */
  public LS_RequestPacket lsReqContent;

  // - - - - - - - - - - - - - - - Neighbor Timers - - - - - - - - - - - - - - - - - //

  /** Single shot timer whose firing indicates that no Hello Packet has been seen from
   *  this Neighbor recently.
   */
  public InactivityTimer deadTimer;

  /** Multiple shot timer whose firing indicates that a Database Description or Request
   *  Packet should be retransmitted.
   */
  public RetransmissionTimer retransTimer;

  /** Multiple shot timer whose firing indicates that LSAs should be reflooded. */
  public FloodingTimer floodTimer;


  // -------------------------- constructor Neighbor -------------------------- //
  /**
   * Constructs an Neighbor.
   */
  public Neighbor(OSPF myOSPF, AreaData myArea, Interface myInterface, int ipaddr) {
    this.myOSPF        = myOSPF;
    this.myArea        = myArea;
    this.myInterface   = myInterface;
    state              = DOWN;
    isMaster           = false;
    DDSeqNum           = (int)(myOSPF.randomStream.nextDouble() * 2147483647.0);
    neighborIPAddr     = ipaddr;
    lastDDPacket       = new LastRecDDPacket();
    floodTimer         = new FloodingTimer(myOSPF, myInterface.rxmtInterval);
    floodTimer.cancel();
  }

  // ------------------------------ Neighbor.reset ------------------------------- //
  /**
   *  reset the Neighbor Data Structure to the initial values. This is part of the 
   *  stop process of the OSPF Session.
   */
  public void reset() {
    if (deadTimer != null) {
      deadTimer.cancel();
      deadTimer = null;
    }
    if (retransTimer != null) {
      retransTimer.cancel();
      retransTimer = null;
    }
    
    floodTimer.cancel();

    state = DOWN;
    isMaster = false;
    DDSeqNum = (int)(myOSPF.randomStream.nextDouble() * 2147483647.0);
    lastDDPacket.clear();
    neighborPrio = 0;
    neighborID   = 0;
    neighborNHI  = "";
    options      = new boolean[8];
    neighborDR   = 0;
    neighborBDR  = 0;
    lsRetransList.clear();
    dbSummList.clear();
    lsReqList.clear();
    pacingFloodList.clear();
    dataDescContent = null;
    lsReqContent = null;
  }
  
  // -------------------------- Neighbor.oneWayHelloRec ---------------------------------- //
  /**
   * Have seen a Neighbor, but the Neighbor doesn't know about me
   */
  public void oneWayHelloRec(Packet helloPkt, int sourceIP) {
    HelloPacket helloContent = (HelloPacket)helloPkt.payload();
    if (deadTimer != null) {
      // cancel Inactivity Timer
      deadTimer.cancel();
    }
    if (retransTimer != null) {
      // cancel Retransmission Timer
      retransTimer.cancel();
    }
    if (state == DOWN) {
      state = INIT;
    } else {
      if (state >= TWOWAY) {
        state = INIT;
        floodTimer.cancel();
        lsRetransList.clear();
        dbSummList.clear();
        lsReqList.clear();
        pacingFloodList.clear();
      }
    }
    if (neighborNHI != helloPkt.nh) neighborNHI = helloPkt.nh;
    if (neighborID != helloPkt.router_ID) neighborID = helloPkt.router_ID;
    if (myInterface.networkType == Interface.PTP) {
      // actions to be performed if the linklayer IS point2point
      if (neighborIPAddr != sourceIP) neighborIPAddr = sourceIP;
    } else {
      // actions to be performed if the linklayer is NOT point2point
      if (neighborPrio != helloContent.routerPrio) neighborPrio = helloContent.routerPrio;
      if (neighborDR != helloContent.DR) neighborDR = helloContent.DR;
      if (neighborBDR != helloContent.BDR) neighborBDR = helloContent.BDR;
    }
    if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true)
      myOSPF.printMessage("OneWay Hello received! Neighbor state of OSPF Router " + neighborNHI +
                          ": " + getStrFromState(state));
    deadTimer = new InactivityTimer(myOSPF, (long)myInterface.routerDeadInterval * SSF.Net.Net.seconds(1.0));
    deadTimer.set((long)myInterface.routerDeadInterval * SSF.Net.Net.seconds(1.0));
  }

  // -------------------------- Neighbor.twoWayHelloRec ---------------------------------- //
  /**
   * Determine whether an adjacency should be established with the neighbor
   */
  public void twoWayHelloRec(Packet helloPkt, int sourceIP) {
    HelloPacket helloContent = (HelloPacket)helloPkt.payload();
    boolean debugOn = (myOSPF.debugOptions[9] || myOSPF.debugOptions[2]);
    boolean debugPrinted = false;

    if (deadTimer != null) {
      deadTimer.cancel();
    }
    if (neighborNHI != helloPkt.nh) neighborNHI = helloPkt.nh;
    if (neighborID != helloPkt.router_ID) neighborID = helloPkt.router_ID;
    if (myInterface.networkType == Interface.PTP) {
      // actions to be performed if the linklayer IS point2point
      if (neighborIPAddr != sourceIP) neighborIPAddr = sourceIP;
      if (state < TWOWAY){
        // state transitions to EXSTART for the first Time
        state = EXSTART;
        if (debugOn) {
          myOSPF.printMessage("TwoWay Hello received! Neighbor state of OSPF Router " + neighborNHI +
                              ": " + getStrFromState(state));
          debugPrinted = true;
        }
        initDBExchange();
      }
    } else {
      // todo: implement actions to be performed if the linklayer is NOT point2point
      if (neighborPrio != helloContent.routerPrio) neighborPrio = helloContent.routerPrio;
      if (neighborDR != helloContent.DR) neighborDR = helloContent.DR;
      if (neighborBDR != helloContent.BDR) neighborBDR = helloContent.BDR;
    }
    if (debugOn && !debugPrinted) {
      myOSPF.printMessage("TwoWay Hello received! Neighbor state of OSPF Router " + neighborNHI +
                          ": " + getStrFromState(state));
    }
    deadTimer = new InactivityTimer(myOSPF, (long)myInterface.routerDeadInterval * SSF.Net.Net.seconds(1.0));
    deadTimer.set((long)myInterface.routerDeadInterval * SSF.Net.Net.seconds(1.0));
  }

  // ------------------------- Neighbor.twoWayReceived ------------------------------ //
  /** called when a DD Packet is received while state was INIT.
   */
  public void twoWayReceived(Packet dataDescPkt) {
    if (myInterface.networkType == Interface.PTP) {
      state = EXSTART;
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true) {
	myOSPF.printMessage("TwoWay received! Neighbor state of OSPF Router " + neighborNHI + ": " +
                            getStrFromState(state));
      }
      initDBExchange();
      if (deadTimer != null) {
        deadTimer.cancel();
      }
      deadTimer = new InactivityTimer(myOSPF, (long)myInterface.routerDeadInterval * SSF.Net.Net.seconds(1.0));
      deadTimer.set((long)myInterface.routerDeadInterval * SSF.Net.Net.seconds(1.0));
      negotiation(dataDescPkt);
    }
  }

  // -------------------------- Neighbor.negotiation ---------------------------------- //
  /** called when a DD Packet is received while state was EXSTART, or state turned to EXSTART.
   */
  public void negotiation(Packet dataDescPkt) {
    DD_Packet recDDCont = (DD_Packet)dataDescPkt.payload();
    boolean init = recDDCont.initialize;
    boolean more = recDDCont.more;
    boolean isM  = recDDCont.isMaster;
    if (init && more && isM) {
      if (recDDCont.isEmpty() && dataDescPkt.router_ID > myOSPF.rtrid) {
	// stop retransmission timer started when turning to state EXSTART.
	retransTimer.cancel();
        options = recDDCont.options;
	// DD Packet is accepted for the first time. Save it for discovering duplicates.
        lastDDPacket = new LastRecDDPacket();
        lastDDPacket.set(init, more, isM, recDDCont.options, recDDCont.DD_seq_num);
        // This router is now slave.
        DDSeqNum = recDDCont.DD_seq_num;
        negotiationDone(true, recDDCont.getHeaders());
      } else {
	// DD Packet is ignored!
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
	  myOSPF.printMessage("DD Packet is ignored!");
      }
    } else
      if (!init && !isM) {
	if ((recDDCont.DD_seq_num == DDSeqNum) && dataDescPkt.router_ID < myOSPF.rtrid) {
          // stop retransmission timer started when turning to state EXSTART.
	  retransTimer.cancel();
          options = recDDCont.options;
	  // DD Packet is accepted for the first time. Save it for discovering duplicates.
          lastDDPacket = new LastRecDDPacket();
          lastDDPacket.set(init, more, isM, recDDCont.options, recDDCont.DD_seq_num);
          // This router is now master.
          DDSeqNum++;
          negotiationDone(false, recDDCont.getHeaders());
        } else {
          // DD Packet is ignored!
          if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
	    myOSPF.printMessage("DD Packet is ignored!");
        }
      } else {
        // DD Packet is ignored!
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
	  myOSPF.printMessage("DD Packet is ignored!");
      }
  }

  // --------------------------- Neighbor.negotiationDone ----------------------------- //
  /**
   * At this point Master/Slave relationship is definitly established. DD sequence numbers
   * have been exchanged. This is the begin of sending/receiving of DD Packets.
   */
  public void negotiationDone(boolean neighborIsMaster, Vector payload) {
    LSA nextLSA;
    state = EXCHANGE;
    isMaster = neighborIsMaster;
    if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true) {
      String msg;
      if (neighborIsMaster) {
	msg = new String(" I'am Slave. ");
      } else {
        msg = new String(" I'am Master. ");
      }
      myOSPF.printMessage("Negotiation done!" + msg + "Neighbor state of OSPF Router " + neighborNHI +
                          " is now: " + getStrFromState(state));
    }
    // fill the database summary list with the Headers of all LSAs having Age lower than
    // MAX_AGE and currently stored in the LSDatabase (there are only Router- and Summary-LSAs 
    // at the moment!)
    for (Enumeration lsaHeaders = myArea.getLSAHeaders().elements(); 
         lsaHeaders.hasMoreElements(); ) {
      nextLSA = (LSA)lsaHeaders.nextElement();
      if (nextLSA.lsAge == myOSPF.MAX_AGE)
	lsRetransList.put(nextLSA.getKey(), nextLSA);
      else
        dbSummList.add(nextLSA);
    }
    if (!isMaster) {
      // We are Master!
      // Further process contents of received DD Packet...
      processLSAs(payload);
      // ...construct new DD Packet...
      dataDescContent = new DD_Packet(myOSPF.MTU, myArea.options, false, true, true, DDSeqNum);
    } else {
      // We are Slave! 
      // No further processing of DD Packet because payload is Empty.
      // Construct new DD Packet...
      dataDescContent = new DD_Packet(myOSPF.MTU, myArea.options, false, true, false, DDSeqNum);
    }
    // ...fill it with as many LSA Headers from the database summary list as possible...
    fillDDPacketWithHeaders();
    // ...and send a copy of it to the Neighbor.
    sendPacket(dataDescContent.copy());
    if (!isMaster) {
      // We are Master so we have to start the retransmission timer.
      retransTimer = new RetransmissionTimer(myOSPF, myInterface.rxmtInterval, dataDescContent);
      retransTimer.set(myInterface.rxmtInterval);
    }
  }

  // ---------------------------- Neighbor.processLSAs -------------------------------- //
  /** 
   *  Process the LSA Headers received in the last received Database Description Packet. 
   *  @param ddPayload A Vector holding the LSA Headers to process.
   */
  public void processLSAs(Vector ddPayload) {
    LSA nextLSA;
    for (Enumeration lsas = ddPayload.elements(); lsas.hasMoreElements(); ) {
      nextLSA = (LSA)lsas.nextElement();
      // check LSA Type.
      if (!hasKnownLSType(nextLSA)) {
        // unknown lsType found!
        seqNumMismatch("LS Type found was unknown.");
        return;
      }
      if ((nextLSA.lsType == LSA.AS_EXTERNAL_LSA) && (options[6] == false)) {
	// LSA is external and the Area has no external lsa capability
        seqNumMismatch("External LSA found although area is stub.");
        return;
      }
      if (myArea.shouldRequest(nextLSA)) {
	// The received LSA is newer than the last instance or there is no instance stored
	// in the database. So request it!
	lsReqList.put(nextLSA.getKey(), nextLSA);
      }
    }
  }

  // ----------------------- Neighbor.fillDDPacketWithHeaders ------------------------- //
  /** fill the global DD Packet dataDescContent with as many headers from the database
   *  summary list as possible. Remove every header filled in the Packet from the list.
   *  The amount of headers in the DD Packet is restricted by the OSPF MTU value.
   */
  public void fillDDPacketWithHeaders() {
    int currentLength = Packet.headerByteCount() + dataDescContent.bytecount();
    int maxSize = myOSPF.MTU - 20; // subtract a normal IP header.
    LSA nextLSA;
    for (Enumeration enumVar = dbSummList.elements(); enumVar.hasMoreElements(); ) {
      if ((currentLength + LSA.headerByteCount()) > maxSize) break;
      nextLSA = (LSA)enumVar.nextElement();
      dataDescContent.addLSAHeader(nextLSA);
      dbSummList.removeElement(nextLSA);
      currentLength += LSA.headerByteCount();
    }
    if (dbSummList.isEmpty()) dataDescContent.more = false;
  }

  // --------------------------- Neighbor.seqNumMismatch ------------------------------ //
  /**
   * Actions to be performed, if the received DD Packet has 
   * a) an unexpected DD sequence number or
   * b) unexpectedly the Initialize bit set or
   * c) an Options field differing from the last Options field received.
   */
  public void seqNumMismatch(String reason) {
    int formerState = state;
    state = EXSTART;
    lsRetransList.clear();
    dbSummList.clear();
    lsReqList.clear();
    pacingFloodList.clear();
    if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true) {
      myOSPF.printMessage("SeqNumMismatch detected! " + reason + " Neighbor state of OSPF Router " +
                          neighborNHI + " is now: " + getStrFromState(state));
    }
    if (formerState == FULL) myArea.createRouterLSA();
    initDBExchange();
  }

  // ----------------------------- Neighbor.badLSReq ---------------------------------- //
  /**
   *  Called if a LS Request has been received for an LSA not contained in the database.
   *  This indicates an error in the Database Exchange process. Actions to be performed
   *  are the same as in seqNumMismatch. In addition, stop the possibly activated retrans-
   *  mission timer.
   */
  public void badLSReq() {
    int formerState = state;
    if (retransTimer != null) {
      retransTimer.cancel();
    }
    state = EXSTART;
    lsRetransList.clear();
    dbSummList.clear();
    lsReqList.clear();
    pacingFloodList.clear();
    if (myOSPF.debugOptions[9] == true) {
      myOSPF.printMessage("Bad LS Request detected! Turn down adjacency. Neighbor state of OSPF Router " + neighborNHI + " is now: " + getStrFromState(state));
    }
    if (formerState == FULL) myArea.createRouterLSA();
    initDBExchange();
  }

  // ----------------------------- Neighbor.exchange ---------------------------------- //
  /** called if state is EXCHANGE. This method is executed every time a DD Packets arrives
   *  sent by the Neighbor. When the last Packet arrives, it transfers the state into
   *  LOADING or FULL
   */
  public void exchange(Packet dataDescPkt) {
    DD_Packet recDDCont = (DD_Packet)dataDescPkt.payload();
    if (!lastDDPacket.checkOnDuplicate(recDDCont)) {
      // Packet is NOT a duplicate, do further checks.
      if (recDDCont.isMaster != isMaster) {
	// Master/Slave bit is inconsistent.
        seqNumMismatch("State of MS-bit is inconsistent.");
        return;
      }
      if (recDDCont.initialize == true) {
	// The initialize bit is set.
        seqNumMismatch("Init-bit unexpectedly set.");
        return;
      }
      if (!Arrays.equals(recDDCont.options, options)) {
	// The optional capabilities have changed.
	seqNumMismatch("Option field received differs.");
	return;
      }
      if (!isMaster) {
	// We are Master, check received seq num.
	if (recDDCont.DD_seq_num == DDSeqNum) {
	  // received Packet is a correct Ack.
          // stop retransmission timer.
	  retransTimer.cancel();
          lastDDPacket.set(recDDCont.initialize, recDDCont.more, recDDCont.isMaster, 
                           recDDCont.options, recDDCont.DD_seq_num);
          DDSeqNum++;
          processLSAs(recDDCont.getHeaders());
          if ((dbSummList.isEmpty()) && (recDDCont.more == false)) {
	    // generate the neighbor event ExchangeDone.
	    exchangeDone();
          } else {
	    // send a new Database Description Packet to the slave.
            if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true) {
              myOSPF.printMessage("DD Packet processed, sending a new DD Packet! Neighbor state of OSPF Router " + neighborNHI + " is now: " + getStrFromState(state));
            }
            dataDescContent = new DD_Packet(myOSPF.MTU, myArea.options, false, true, true, DDSeqNum);
            fillDDPacketWithHeaders();
            sendPacket(dataDescContent.copy());
            retransTimer = new RetransmissionTimer(myOSPF, myInterface.rxmtInterval, dataDescContent);
            retransTimer.set(myInterface.rxmtInterval);
          }
          return;
        }
      } else {
	// We are Slave, check received seq num.
        if (recDDCont.DD_seq_num == (DDSeqNum + 1)) {
	  // received Packet is in order.
          lastDDPacket.set(recDDCont.initialize, recDDCont.more, recDDCont.isMaster, 
                           recDDCont.options, recDDCont.DD_seq_num);
          DDSeqNum = recDDCont.DD_seq_num;
          processLSAs(recDDCont.getHeaders());
          // send a DD Packet in reply.
          dataDescContent = new DD_Packet(myOSPF.MTU, myArea.options, false, true, false, DDSeqNum);
          fillDDPacketWithHeaders();
          sendPacket(dataDescContent.copy());
          if ((recDDCont.more == false) && (dataDescContent.more == false)) {
            // generate the neighbor event ExchangeDone.
	    exchangeDone();
          } else {
            if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
              myOSPF.printMessage("DD Packet processed, sending DD Packet back! Neighbor state of OSPF Router " + neighborNHI + " is now: " + getStrFromState(state));
          }
          return;
        }
      }
      // received seq num was not in order, generate the event seq num mismatch.
      seqNumMismatch("Received Seq Num not in order.");
    } else {
      // Packet IS a duplicate
      if (!isMaster) { 
        // We are Master, discard the Packet!
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
          myOSPF.printMessage("DD Packet discarded! Packet was a duplicate. Neighbor state of OSPF Router " + neighborNHI + " is now: " + getStrFromState(state));
      } else {
        // We are Slave, retransmit the Database Description Packet last sent.
        if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
          myOSPF.printMessage("DD Packet was a duplicate! Retransmit last sent DD Packet. Neighbor state of OSPF Router " + neighborNHI + " is now: " + getStrFromState(state));
        sendPacket(dataDescContent.copy());
      }
    }
  }

  // ----------------------------- Neighbor.exchangeDone ------------------------------- //
  /**
   *  At this point, the router has sent and received an entire sequence of Database
   *  Description Packets. Now it must be determined whether the new state is FULL, or
   *  Link State Request Packets have to be send.
   */
  public void exchangeDone() {
    boolean debugOn = (myOSPF.debugOptions[9] || myOSPF.debugOptions[3]);

    if (lsReqList.isEmpty()) {
      state = FULL;
      if (debugOn) {
        myOSPF.printMessage("Exchange done! Neighbor state of OSPF Router " + neighborNHI +
                            " is now: " + getStrFromState(state));
      }
      // generate a new Router-LSA.
      myArea.createRouterLSA();
    } else {
      state = LOADING;
      if (debugOn) {
        myOSPF.printMessage("Exchange done! Neighbor state of OSPF Router " + neighborNHI +
                            " is now: " + getStrFromState(state));
      }
      // start or continue (?) sending Link State Request Packets to the neighbor.
      lsReqContent = new LS_RequestPacket();
      fillRequestPacket();
      sendPacket(lsReqContent.copy());
      retransTimer = new RetransmissionTimer(myOSPF, myInterface.rxmtInterval, lsReqContent);
      retransTimer.set(myInterface.rxmtInterval);
    }
  }

  // ---------------------------- Neighbor.subseqTreatment ----------------------------- //
  /** called by the Interface Data Structure if a DD Packet was received and the relationship 
   *  with the neighbor is in state LOADING or FULL. The only DD Packets received should 
   *  be duplicates.
   */
  public void subseqTreatment(Packet dataDescPkt) {
    DD_Packet recDDCont = (DD_Packet)dataDescPkt.payload();
    if (lastDDPacket.checkOnDuplicate(recDDCont)) {
      // Packet is a duplicate.
      if (Arrays.equals(recDDCont.options, options)) {
	// The optional capability remains the same.
        if (!isMaster) { 
          // We are Master, discard the Packet!
          if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
            myOSPF.printMessage("DD Packet discarded! Packet was a duplicate. Neighbor state of OSPF Router " + neighborNHI + " is now: " + getStrFromState(state));
        } else {
          // We are Slave, retransmit the Database Description Packet last sent.
          if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true)
            myOSPF.printMessage("DD Packet was a duplicate! Retransmit last sent DD Packet. Neighbor state of OSPF Router " + neighborNHI + " is now: " + getStrFromState(state));
          sendPacket(dataDescContent.copy());
        }
      } else seqNumMismatch("Option field received differs.");
    } else seqNumMismatch("DD Packet was not a duplicate while state was "+getStrFromState(state)+".");
  }

  // --------------------------------- Neighbor.adjOk ---------------------------------- //
  /**
   * A decision must be made as to whether an adjacency should be established/maintained with
   * the neighbor. 
   */
  public void adjOk() {
    if (state == TWOWAY) {
      
    }
  }

  // --------------------------- Neighbor.fillRequestPacket -------------------------- //
  /** fill the global LS Request Packet lsReqContent with the keys of as many LSAs from the 
   *  link state request list as possible. The amount of keys in the LS Request Packet is 
   *  restricted by the OSPF MTU value.
   */
  public void fillRequestPacket() {
    int currentLength = Packet.headerByteCount();
    String key;
    for (Enumeration keys = lsReqList.keys(); keys.hasMoreElements(); ) {
      if ((currentLength + 12) > myOSPF.MTU) break;
      key = (String)keys.nextElement();
      lsReqContent.put(key);
      currentLength += 12;
    }
  }

  // --------------------------- Neighbor.fillAndSendUpdate ------------------------- //
  /** Fill an LS Update Packet with requested LSAs and send it to the Neighbor. If there
   *  are more LSAs requested than could sent by a single LS Update Packet, return the
   *  rest of the requests to handle in a Vector. Else return an empty Vector.
   */
  public Vector fillAndSendUpdate(Vector requests) throws ProtocolException {
    LSA nextLSA;
    String key;
    int lsaSize;
    LS_UpdatePacket lsUpdContent = new LS_UpdatePacket();
    int currentLength = Packet.headerByteCount();
    int elemNumber = 0;
    for (Enumeration keys = requests.elements(); keys.hasMoreElements(); ) {
      key = (String)keys.nextElement();
      nextLSA = myArea.getLSA(key);
      // If there was no instance of the requested LSA in the database, then it is a bad request.
      if (nextLSA == null) throw new ProtocolException("BadLSRequest");
      // We have an instance of the requested LSA.
      lsaSize = nextLSA.bytecount();
      if ((currentLength + lsaSize) > myOSPF.MTU) break; //Packet is full!
      lsUpdContent.put(nextLSA);
      elemNumber++;
      currentLength += lsaSize;
    }
    sendPacket(lsUpdContent);
    for (int i = 0; i < elemNumber; i++) requests.remove(0);
    return requests;
  }

  // --------------------------- Neighbor.processLSRequest -------------------------- //
  /** examine the requested LSAs, locate them in the database and send them in LS Update
   *  Packets to the Neighbor.
   */
  public void processLSRequest(Packet lsReqPkt) {
    LS_RequestPacket recLSReqCont = (LS_RequestPacket)lsReqPkt.payload();
    Vector requests = recLSReqCont.getRequests();
    if (requests.isEmpty()) {
      // No requests at all
      if (myOSPF.debugOptions[9] == true)
        myOSPF.printMessage("LS Request was empty! Packet ignored. Neighbor state of OSPF Router " +
                            neighborNHI + " is now: " + getStrFromState(state));
      return;
    }
    try {
      while (!requests.isEmpty()) {
        // As long as there are requests to handle, send LS Update Packets
        requests = fillAndSendUpdate(requests);
      }
    } catch (ProtocolException e) {
      badLSReq();
    }
  }

  // --------------------------- Neighbor.processLSUpdate --------------------------- //
  /** LS Update Packet has been received while state was EXCHANGE or LOADING. Examine the 
   *  received LSAs, check whether they were requested or not and process them accordingly.
   *  Therefore use method "processReceivedLSA" for further treatment.
   *  
   *  @param lsUpdPkt an LS Update Packet received while Neighbor state was EXCHANGE or
   *                  LOADING.
   */
  public void processLSUpdate(Packet lsUpdPkt) {
    LS_UpdatePacket recLSUpdCont;
    Vector lsas;
    Vector requests;
    LSA nextLSA;
    String lsaKey;

    recLSUpdCont = (LS_UpdatePacket)lsUpdPkt.payload();
    lsas = recLSUpdCont.getLSAs();
    if (lsas.isEmpty()) {
      // No LSAs found in LS Update Packet.
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true)
        myOSPF.printMessage("LS Update was empty! Packet ignored. Neighbor state of OSPF Router " +
                            neighborNHI + " is now: " + getStrFromState(state));
      return;
    } else {
      // process the received LSAs
      // load the Headers of the requested LSAs (if any) to compare with received ones
      if(lsReqContent == null) {
	requests = new Vector();
      } else {
        requests = lsReqContent.getRequests();
      }
      for (Enumeration lsaList = lsas.elements(); lsaList.hasMoreElements(); ) {
	nextLSA = (LSA)lsaList.nextElement();
        lsaKey = nextLSA.getKey();
        // check whether nextLSA was or will be requested via an LS Request Packet or was received via 
        // flooding.
        if (lsReqList.containsKey(lsaKey)) {
	  // LSA is on the LS Request List and therefore was or will be requested.
	  boolean removeIt;
	  try {
	    removeIt = processReceivedLSA(nextLSA);
            if (removeIt) removeRequest(lsaKey);
	  } catch (ProtocolException protExcp) {
	    return; // A bad LS Request was detected.
	  }
	} else {
          // LSA was received via flooding.
	  try {
	    processReceivedLSA(nextLSA);
	  } catch (ProtocolException protExcp) {
	    return; // A bad LS Request was detected.
	  }
        }
      }
      if ((lsReqContent != null) && lsReqContent.isEmpty()) {
	// All LSAs requested in the current LS Request Packet have been received.
	if (lsReqList.isEmpty() && (state == LOADING)) {
	  // Database is synchronized! (Hopfully) 
	  loadingDone();
        } else {
	  // Create and send a new LS Request Packet
          if (retransTimer != null) {
            retransTimer.cancel();
          }
          fillRequestPacket();
          sendPacket(lsReqContent.copy());
          retransTimer = new RetransmissionTimer(myOSPF, myInterface.rxmtInterval, lsReqContent);
          retransTimer.set(myInterface.rxmtInterval);
        }
      }
    }
  }

  //--------------------------- Neighbor.removeRequest ---------------------------------- //
  /** Remove a key from the LS Request List. If there is an LS Request Packet ready to send
   *  or the Packet is waiting for updates it requested and it holds this key, then remove 
   *  it from the packets content too.
   *
   *  @param removeKey A String representing the key which shall be removed.
   */
  protected void removeRequest(String removeKey) {
    lsReqList.remove(removeKey);
    if (lsReqContent != null) {
      // An LS Request Packet is pending, so remove the key, possible in its body.
      lsReqContent.getRequests().remove(removeKey);
    }
  }

  // ------------------------ Neighbor.processReceivedLSA ------------------------------ //
  /** This method is called by either "processLSUpdate" or "processFloodedUpdate". It gets
   *  recLSA from a received LS Update Packet and processes it as decribed in chapter 13 
   *  on page 143-146 in RFC 2328. The method returns "true" if recLSA should be removed
   *  from the LS Retransmission List in the case it has been placed there before, otherwise 
   *  "false".
   *  
   *  @param recLSA LSA which was received in an LS Update Packet.
   *  @exception ProtocolException thrown if a Bad LS Request is triggered.
   *  @return boolean indicating whether recLSA should be removed from LS Retransmission List or not.
   */
  public boolean processReceivedLSA(LSA recLSA) throws ProtocolException {
    // boolean indicating that recLSA was not requested by a Request Packet. 
    boolean wasNotRequested;

    // determine whether recLSA was received due to a request or due to flooding
    if (lsReqContent != null) {
      wasNotRequested = !lsReqContent.getRequests().contains(recLSA.getKey());
    } else {
      wasNotRequested = true;
    }

    if (!hasKnownLSType(recLSA))
      // RFC 2328 Chapter 13. (2) page 143
      return true;
    if ((recLSA.lsType == LSA.AS_EXTERNAL_LSA) && (myArea.options[6] == false))
      // RFC 2328 Chapter 13. (3) page 143
      return true;
    if ((recLSA.lsAge == myOSPF.MAX_AGE) && (!myArea.hasInstanceOf(recLSA)) && 
      myArea.noNeighborInDataExProcess()) {
      // RFC 2328 Chapter 13. (4) page 143/144 (excluding AS_EXTERNAL_LSAs)
      if (wasNotRequested) {
	// Because the LSA was not yet requested, it is treated as a flooded LSA and thus
	// acknowledged.
        recLSA.dropPayload();
        directAcknowledge(recLSA);
      }
      return true;
    }
    if (myArea.shouldRequest(recLSA)) {
      // RFC 2328 Chapter 13. (5) page 144 (excluding AS_EXTERNAL_LSAs)
      // There is no database copy OR received LSA is more recent than database copy.
      if (myOSPF.isSelforiginated(recLSA)) {
        // RFC 2328 Chapter 13. (5)(f) page 145 received LSA is selforiginated.
        myArea.advanceSeqNumOf(recLSA);
        return true;
      }
      boolean isInstalled; // indicates whether installation was successful or not.
      isInstalled = myArea.installReceivedLSA(recLSA, neighborID);
      if (!isInstalled) return false;
      // TODO: don't forget the case of receiving DR interface and sending interface other than BR
      if (myInterface.state == Interface.BDR) {
	// TODO: if (advertisement received from DR) myInterface.delayedAcknowledge(recLSA.copy());
      } else {
        if (wasNotRequested) {
	  // Because the LSA was not yet requested, it is treated as a flooded LSA and thus
	  // acknowledged.
          myInterface.delayedAcknowledge((LSA)recLSA.copy());
        }
      }
      return true;
    }
    if (lsReqList.containsKey(recLSA.getKey())) {
      // RFC 2328 Chapter 13. (6) page 145
      badLSReq();
      throw new ProtocolException("BadLSRequest");
    }
    if (myArea.testInstanceOf(recLSA, "identical")) {
      // RFC 2328 Chapter 13. (7) page 145 (excluding AS_EXTERNAL_LSAs)
      String key = recLSA.getKey();
      if (lsRetransList.containsKey(key)) {
	// LSA is a duplicate, and IS treated as an implied acknowledgment.
        lsRetransList.remove(key);
        if (myInterface.state == Interface.BDR) {
	  // Send an delayed acknowledgment if LSA was received from DR, otherwise do nothing.
        }
      } else {
	// LSA is a duplicate, and is NOT treated as an implied acknowledgment.
        directAcknowledge(recLSA);
      }
      return true;
    }
    if (myArea.testInstanceOf(recLSA, "false")) {
      // RFC 2328 Chapter 13. (8) page 145/146 (excluding AS_EXTERNAL_LSAs)
      if (myArea.lsaIsWrapping(recLSA)) {
	// Discard the received LSA without acknowledging it.
        return true;
      } else {
	// TODO: test whether LSA has not been sent out in an LS Update Packet the last
	// MIN_LS_ARRIVAL seconds.
        LS_UpdatePacket lsUpdContent = new LS_UpdatePacket();
	lsUpdContent.put(myArea.getLSA(recLSA.getKey()));
	// lsUpdContent now contains an LSA. It is assured by the if-branch before.
        sendPacket(lsUpdContent);
      }
    }
    return true; // This line should never be reached!
  }

  // --------------------------- Neighbor.startFlooding ------------------------------- //
  /** The Interface's pacingTimer schedules the first flooding of an LSA. This method groups
   *  together these LSAs and sends out one LS Update Packet. If there are more LSAs that
   *  must be flooded out than fitting in a single packet, it returns true otherwise false.
   * @return boolean value indicating wether the pacingTimer must be started again due to
   *         LSAs left for flooding out, or not.
   */
  public boolean startFlooding() {
    if (pacingFloodList.isEmpty()) return false;
    LSA nextLSA, copyOfNextLSA;
    int lsaSize;
    long newAge;
    // construct a new LS Update Packet...
    LS_UpdatePacket lsUpdContent = new LS_UpdatePacket();
    int currentLength = Packet.headerByteCount();
    
    Object[] pacingList = pacingFloodList.keySet().toArray();
    for (int i=0; i < pacingList.length; i++) {
      // ...and fill it with copys of LSAs from the pacingFloodList.
      nextLSA = (LSA)pacingFloodList.remove(pacingList[i]);
      lsaSize = nextLSA.bytecount();
      if ((currentLength + lsaSize) > myOSPF.MTU) {
	pacingFloodList.put(pacingList[i], nextLSA);
        break; //Packet is full!
      }
      // Don't forget to increment the Age of each LSA by InfTransDelay before sending.
      newAge = nextLSA.lsAge + myInterface.transDelay;
      copyOfNextLSA = (LSA)nextLSA.copy();
      if (newAge >= myOSPF.MAX_AGE) copyOfNextLSA.lsAge = myOSPF.MAX_AGE;
      else copyOfNextLSA.lsAge = newAge; 
      lsUpdContent.put(copyOfNextLSA);
      lsRetransList.put(nextLSA.getKey(), nextLSA);
    }
    sendPacket(lsUpdContent); // send out the new packet
    if (floodTimer.isCancelled()) floodTimer.set(myInterface.rxmtInterval);
    if (!pacingFloodList.isEmpty()) return true;
    return false;
  }

  // --------------------------- Neighbor.hasKnownLSType ------------------------------ //
  /** Check whether testLSA has a known LS Type and return true if so, otherwise false.
   *  Known LS Types are: Router-LSA, Network-LSA, Summary-Network-LSA, Summary-ASBR-LSA
   *  and AS-External-LSA.
   */
  public boolean hasKnownLSType(LSA testLSA) {
    if ((testLSA.lsType == LSA.ROUTER_LSA) || (testLSA.lsType == LSA.NETWORK_LSA) ||
        (testLSA.lsType == LSA.SUMMARY_NETWORK_LSA) || (testLSA.lsType == LSA.SUMMARY_ASBR_LSA) ||
        (testLSA.lsType == LSA.AS_EXTERNAL_LSA)) return true;
    return false;
  }

  // ------------------------ Neighbor.processFloodedUpdate --------------------------- //
  /** LS Update Packet has been received while state was FULL. */
  public void processFloodedUpdate(Packet lsUpdPkt) {
    LS_UpdatePacket recLSUpdCont = (LS_UpdatePacket)lsUpdPkt.payload();
    Vector lsas = recLSUpdCont.getLSAs();
    LSA nextLSA;
    if (lsas.isEmpty()) {
      // No LSAs found in LS Update Packet.
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true)
        myOSPF.printMessage("LS Update was empty! Packet ignored. Neighbor state of OSPF Router " +
                            neighborNHI + " is now: " + getStrFromState(state));
      return;
    } else {
      // process the received LSAs
      for (Enumeration lsaList = lsas.elements(); lsaList.hasMoreElements(); ) {
	nextLSA = (LSA)lsaList.nextElement();
        try {
	  processReceivedLSA(nextLSA);
	} catch (ProtocolException protExcp) {
	  return; // A bad LS Request was detected.
	}
      }
    }
  }

  // ----------------------------- Neighbor.loadingDone ------------------------------- //
  /** Link State Updates have been received for all out-of-date portions of the database. */
  public void loadingDone() {
    state = FULL;
    if (retransTimer != null) {
      retransTimer.cancel();
    }
    if (myOSPF.debugOptions[9] == true)
      myOSPF.printMessage("Loading Done! Neighbor state of OSPF Router " + neighborNHI + " is now: " +
                          getStrFromState(state));
    myArea.createRouterLSA();
  }

  // ------------------------------ Neighbor.sendPacket ------------------------------- //
  /** Sends an OSPF Packet (database description or ls request) to the Neighbor this 
   *  Structure was built for. This method only send the Packet! The parameter content 
   *  must be set appropriately before. 
   */
  public void sendPacket(ProtocolMessage content) {
    IpHeader sendPkt;
    Packet ospfPkt;
    if (content instanceof DD_Packet) {
      ospfPkt = new Packet(Packet.DATABASE, myOSPF.rtrid, myOSPF.nh, myInterface.as_nh, myArea.areaID);
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true) {
	// print debug info
        String message = IP_s.IPtoString(myInterface.ipaddr) + "||Send new DD Packet!||" + 
                         "To OSPF Router " + neighborNHI + " (" + IP_s.IPtoString(neighborID) + ")|" + 
                         IP_s.IPtoString(neighborIPAddr) + "\n";
        message += "   Initialize: " + (new Boolean(((DD_Packet)content).initialize)).toString();
        message += "  More: " + (new Boolean(((DD_Packet)content).more)).toString();
        message += "  Master/Slave: " + (new Boolean(((DD_Packet)content).isMaster)).toString();
        message += "  Seqnum: " + Integer.toHexString(((DD_Packet)content).DD_seq_num);
        myOSPF.printMessage(message);
      }
    } else {
      if (content instanceof LS_RequestPacket) {
        ospfPkt = new Packet(Packet.LSREQUEST, myOSPF.rtrid, myOSPF.nh, myInterface.as_nh, myArea.areaID);
        if (myOSPF.debugOptions[9] == true)
	  // print debug info
          myOSPF.printMessage(IP_s.IPtoString(myInterface.ipaddr) + "||Send new LS Request Packet!||"
          + "To OSPF Router " + neighborNHI + " (" + IP_s.IPtoString(neighborID) + ")|" + 
          IP_s.IPtoString(neighborIPAddr));
      } else {
        if (content instanceof LS_UpdatePacket) {
          ospfPkt = new Packet(Packet.LSUPDATE, myOSPF.rtrid, myOSPF.nh, myInterface.as_nh, myArea.areaID);
          if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true) {
            // print debug info
	    String message = IP_s.IPtoString(myInterface.ipaddr) + "||Send new LS Update Packet!||" +
                             "To OSPF Router " + neighborNHI + " (" + IP_s.IPtoString(neighborID) +
                             ")|" + IP_s.IPtoString(neighborIPAddr);
            Vector upds = ((LS_UpdatePacket)content).getLSAs();
            LSA nextOne;
            for (Enumeration enumVar = upds.elements(); enumVar.hasMoreElements(); ) {
	      nextOne = (LSA)enumVar.nextElement();
              message += "\n   Update for " + nextOne.getKey() + " Seqnum: " + 
		         Integer.toHexString(nextOne.lsSeqnum);
            }
            myOSPF.printMessage(message);
          }
        } else {
          ospfPkt = new Packet(Packet.LSACK, myOSPF.rtrid, myOSPF.nh, myInterface.as_nh, myArea.areaID);
          if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true) {
            // print debug info
	    String message = IP_s.IPtoString(myInterface.ipaddr) + "||Send new LS Ack Packet!||" +
                             "To OSPF Router " + neighborNHI + " (" + IP_s.IPtoString(neighborID) + 
		             ")|" + IP_s.IPtoString(neighborIPAddr);
            Vector acks = ((LS_AckPacket)content).getHeaders();
            LSA nextOne;
            for (Enumeration enumVar = acks.elements(); enumVar.hasMoreElements(); ) {
	      nextOne = (LSA)enumVar.nextElement();
              message += "\n   Ack for " + nextOne.getKey() + " Seqnum: " + 
		         Integer.toHexString(nextOne.lsSeqnum);
            }
            myOSPF.printMessage(message);
          }
        }
      }
    }
    content.dropHeader();
    ospfPkt.carryPayload(content);
    sendPkt = new IpHeader(myOSPF.PROTOOSPF, myInterface.ipaddr, neighborIPAddr, 1);
    sendPkt.carryPayload(ospfPkt);
    try {
      myOSPF.ip.push(sendPkt, myOSPF);
    } catch (ProtocolException PExc) {
	myOSPF.printError("Interface", IP_s.IPtoString(myInterface.ipaddr), "OSPF Packet could not be sent!");
    }
  }

  // ------------------------------ Neighbor.initDBExchange -------------------------- //
  /** initializes the Database Exchange between two Neighbors */
  public void initDBExchange() {
    DDSeqNum++;
    isMaster = false;
    dataDescContent = new DD_Packet(myOSPF.MTU, myArea.options, true, true, true, DDSeqNum);
    sendPacket(dataDescContent.copy());
    retransTimer = new RetransmissionTimer(myOSPF, myInterface.rxmtInterval, dataDescContent);
    retransTimer.set(myInterface.rxmtInterval);
  }

  // ------------------------------ Neighbor.getStrFromState ------------------------- //
  /**
   * Converts the integer value of state in a more readable format. This is only used
   * for debuging purposes.
   */
  public String getStrFromState(int status) {
    switch (status) {
    case DOWN:
      return ("DOWN");
    case ATTEMPT:
      return ("ATTEMPT");
    case INIT:
      return ("INIT");
    case TWOWAY:
      return ("TWOWAY");
    case EXSTART:
      return ("EXSTART");
    case EXCHANGE:
      return ("EXCHANGE");
    case LOADING:
      return ("LOADING");
    case FULL:
      return ("FULL");
    default:
      return("");
    }
  }

  // -------------------------- Neighbor.directAcknowledge ------------------------- //
  /** Send a direct Acknowledgment for a particular LSA to the Neighbor. */
  public void directAcknowledge(LSA ackLSA) {
    LS_AckPacket ackContent = new LS_AckPacket();
    ackContent.put(ackLSA);
    sendPacket(ackContent);
  }

  // ---------------------------- Neighbor.processLSAck ---------------------------- //
  /** When an LS Ack Packet was received, this method is called by the Interface which
   *  is adjacent to this Neighbor. It then processes the received Packet.
   */
  public void processLSAck(Packet lsAckPkt) {
    LS_AckPacket recLSAckCont = (LS_AckPacket)lsAckPkt.payload();
    Vector headers = recLSAckCont.getHeaders();
    LSA nextLSA;
    String key, compare;
    if (headers.isEmpty()) {
      // No headers found in LS Ack Packet.
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true)
        myOSPF.printMessage("LS Ack was empty! Packet is ignored. Neighbor state of OSPF Router " + 
                            neighborNHI + " is now: " + getStrFromState(state));
      return;
    } else {
      // process the received LSA Headers
      for (Enumeration acks = headers.elements(); acks.hasMoreElements(); ) {
	nextLSA = (LSA)acks.nextElement();
        key = nextLSA.getKey();
        if (lsRetransList.containsKey(key)) {
	  compare = myArea.database.isNewerThan(nextLSA, (LSA)lsRetransList.get(key));
          if (compare.equals("identical")) {
            lsRetransList.remove(key);
          } else {
            myOSPF.printMessage("Received Acknowledgment for unknown instance of LSA "+nextLSA.getKey()
                               +" (received seqnum: "+nextLSA.lsSeqnum+" expected seqnum: "
                               +((LSA)lsRetransList.get(key)).lsSeqnum+")! Please log.");
          }
        } else {
	  continue;
        }
      }
    }
  }

  // ======================= inner class LastRecDDPacket ====================== //
  /**
   * A class in which the important information of the last received DD packet are 
   * recorded. These are the Initialize, More and Master bits the options and the
   * sequence number.
   */

  public class LastRecDDPacket {

    public boolean initialize;
    public boolean more;
    public boolean isMaster;
    public boolean[] options;
    public int DDSeqNum;
    
    // ------------------------ Constructor LastRecDDPacket ----------------------------- //
    public LastRecDDPacket() {
      // maybe used in the future
    }

    // ---------------------------- LastRecDDPacket.set ---------------------------------- //
    /** saves the important values for identifying duplicates of a new received DD Packet */
    public void set(boolean init, boolean mo, boolean ma, boolean[] opt, int DDSeqNum) {
      initialize = init;
      more       = mo;
      isMaster     = ma;
      options    = opt;
      this.DDSeqNum = DDSeqNum;
    }

    // --------------------------- LastRecDDPacket.clear ------------------------------- //
    /** clears this structure. Used when the Inactivity Timer expires. */
    public void clear() {
      initialize = false;
      more = false;
      isMaster = false;
      options = new boolean[8];
      this.DDSeqNum = 0;
    }

    // ----------------------- LastRecDDPacket.checkOnDuplicate ------------------------ //
    /** checks whether the DD Packet just received is a duplicate. Returns true if it is a 
     *  duplicate, else false.
     */
    public boolean checkOnDuplicate(DD_Packet content) {
      if ((lastDDPacket.initialize == content.initialize) && (lastDDPacket.more == content.more) &&
          (lastDDPacket.isMaster == content.isMaster) && 
          Arrays.equals(lastDDPacket.options, content.options) && 
          (lastDDPacket.DDSeqNum == content.DD_seq_num)) return true;
      return false;
    }
    
  } // end of class Last_Received_DD_packet.


  // =============================== inner class RetransmissionTimer ================================ //
  /**
  /* The Retransmission Timer for this Neighbor class. A single shot timer whose firing indicates 
   * that a Database Description Packet should be resend, because no appropriate answer (i.e. a DD
   * Packet) from the adjacent Neighbor were received in the last rxmtInterval seconds.
   */
  
  public class RetransmissionTimer extends Timer {

    /** content of the Packet currently to resend. */
    public ProtocolMessage pktContent;
    
    // -------------------------- constructor RetransmissionTimer ----------------------------- //
    public RetransmissionTimer(OSPF o, long dt, ProtocolMessage pktContent) {
      super(o.inGraph(), dt);
      this.pktContent = pktContent;
    }

    // -------------------------- RetransmissionTimer.callback -------------------------------- //
    /** Action to be taken upon expiration if the timer has not been canceled. */
    public void callback() {
      if (state > TWOWAY) {
	// send a copy of the Content. The Neighbor should only have a copy of this Content 
	// to protect it from manipulation by the Neighbor. 
        sendPacket(pktContent.copy());
        this.set(myInterface.rxmtInterval);
      }
    }

  } // end of class RetransmissionTimer

  // =============================== inner class InactivityTimer ================================ //
  /**
  /* The Inactivity Timer for this Neighbor class. A single shot timer whose firing indicates 
   * that no Hello Packet has been seen from this neighbor recently.  The length of the
   * timer is RouterDeadInterval seconds.
   */
  
  public class InactivityTimer extends Timer {
    
    // -------------------------- constructor InactivityTimer ----------------------------- //

    public InactivityTimer(OSPF o, long dt) {
      super(o.inGraph(), dt);
    }

    // -------------------------- InactivityTimer.callback -------------------------------- //
    /** Action to be taken upon expiration if the timer has not been canceled. */

    public void callback() {
      int formerState = state;
      if (retransTimer != null) {
        retransTimer.cancel();
        retransTimer = null;
      }
      floodTimer.cancel();

      state           = DOWN;
      lsRetransList.clear();
      dbSummList.clear();
      lsReqList.clear();
      pacingFloodList.clear();
      isMaster        = false;
      DDSeqNum        = (int)(myOSPF.randomStream.nextDouble() * 2147483647.0);
      lastDDPacket.clear();
      neighborPrio    = 0;
      neighborID      = 0;
      options         = new boolean[8];
      neighborDR      = 0;
      neighborBDR     = 0;
      dataDescContent = null;
      lsReqContent    = null;
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true) {
        myOSPF.printMessage("Inactivity Timer expired! Neighbor state of OSPF Router " + neighborNHI +
                            " is now: " + getStrFromState(state));
      }
      if (formerState == FULL) myArea.createRouterLSA();
    }

  } // end of class InactivityTimer

  // =============================== inner class FloodingTimer ================================= //
  /**
  /* A multiple shot Timer for flooding LSAs in LS Update Packets. Its firing indicates that LSAs
   * should be reflooded, because no Acknowledgment for these LSAs from the adjacent Neighbor has 
   * been seen for the last rxmtInterval seconds.
   */
  
  public class FloodingTimer extends Timer {

    // content of the LS Update Packet currently to resend.
    public LS_UpdatePacket lsUpdContent;
    
    // ----------------------------- constructor FloodingTimer ------------------------------- //
    public FloodingTimer(OSPF o, long dt) {
      super(o.inGraph(), dt);
    }

    // ------------------------------- FloodingTimer.callback -------------------------------- //
    /** Action to be taken upon expiration if the timer has not been canceled. */
    public void callback() {
      this.cancel();
      if (state > EXSTART) {
	// if the LS Retransmission List is Empty stop flooding!
	if (lsRetransList.isEmpty()) return;
	// ...else send a new LS Update Packet, filled with LSAs from the LS Retransmission List 
        // to the Neighbor and restart the Timer.
	buildNewUpdate();
        sendPacket(lsUpdContent);
        this.set(myInterface.rxmtInterval);
      }
    }

    // ---------------------------- FloodingTimer.buildNewUpdate ---------------------------- //
    /** construct a new LS Update Packet which will be sent to the Neighbor. */
    public void buildNewUpdate() {
      LSA nextLSA;
      int lsaSize;
      long newAge;
      // construct a new LS Update Packet...
      lsUpdContent = new LS_UpdatePacket();
      int currentLength = Packet.headerByteCount();
      Object[] retrList = lsRetransList.keySet().toArray();
      for (int i=0; i < retrList.length; i++) {
	// ...and fill it with copys of LSAs from the LS Retransmission List.
	nextLSA = myArea.getLSA((String)retrList[i]);
        if (nextLSA == null) {
          System.out.println("Error on OSPF router "+myOSPF.nh+"! LSA "+retrList[i]+
                             " found on lsRetransList for Neighbor "+neighborID+
                             " is not in database!");
          System.exit(-1);
        }
        lsaSize = nextLSA.bytecount();
        if ((currentLength + lsaSize) > myOSPF.MTU) break; //Packet is full!
        // Don't forget to increment the Age of each LSA by InfTransDelay before sending.
        newAge = nextLSA.lsAge + myInterface.transDelay;
        if (newAge >= myOSPF.MAX_AGE) nextLSA.lsAge = myOSPF.MAX_AGE;
        else nextLSA.lsAge = newAge; 
        lsUpdContent.put(nextLSA);
      }
    }

  } // end of class FloodingTimer
  
} // end of class Neighbor.
